package me.jiatao.shirostudy.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import me.jiatao.shirostudy.realm.MD5ShiroRealm;
import me.jiatao.shirostudy.realm.SHA1ShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.*;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {

    /**
     * 将自己的MD5验证方式加入容器
     *
     * @return
     */
    public MD5ShiroRealm md5ShiroRealm() {
        MD5ShiroRealm customShiroRealm = new MD5ShiroRealm();

        //TODO 创建密码匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");//设置使用加密算法
        matcher.setHashIterations(1024);//设置加密次数

        customShiroRealm.setCredentialsMatcher(matcher);
        return customShiroRealm;
    }

    /**
     * 将自己的SHA1验证方式加入容器
     *
     * @return
     */
    public SHA1ShiroRealm sha1ShiroRealm() {
        SHA1ShiroRealm customShiroRealm = new SHA1ShiroRealm();

        //TODO 创建密码匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA1");//设置使用加密算法
        matcher.setHashIterations(1024);//设置加密次数

        customShiroRealm.setCredentialsMatcher(matcher);
        return customShiroRealm;
    }

    /**
     * 设置自定义认证器
     *
     * @return
     */
    @Bean
    public ModularRealmAuthenticator realmAuthenticator() {
        ModularRealmAuthenticator realmAuthenticator = new ModularRealmAuthenticator();
        /** 设置认证实现 */
//        ArrayList<Realm> realms = new ArrayList<>();
//        realms.add(md5ShiroRealm());
//        realms.add(sha1ShiroRealm());
//        realmAuthenticator.setRealms(realms);

        /** 设置认证策略
         *
         * FirstSuccessfulStrategy
         * 只要有一个 Realm 验证成功即可，只返回第 一个 Realm 身份验证成功的认证信息，其他的忽略;
         *
         * AllSuccessfulStrategy
         * 所有Realm验证成功才算成功，且返回所有 Realm身份验证成功的认证信息，如果有一个失败就失败了。
         *
         *
         * AtLeastOneSuccessfulStrategy (默认)
         *只要有一个Realm验证成功即可，和 FirstSuccessfulStrategy 不同，将返回所有Realm身份验证成功的认证信息;
         */
        realmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        return realmAuthenticator;
    }

    /**
     * 权限管理，配置主要是Realm的管理认证
     *
     * @return
     */
    @Bean
    public WebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        /**设置单个Realm*/
        //securityManager.setRealm(md5ShiroRealm());
        /**设置多个Realm 设置认证器*/
        securityManager.setAuthenticator(realmAuthenticator());

        /**
         * 设置多个Realm
         * 初始化的是也会调用 认证器的set方法进行赋值
         */
        ArrayList<Realm> realms = new ArrayList<>();
        realms.add(md5ShiroRealm());
        //realms.add(sha1ShiroRealm());
        securityManager.setRealms(realms);

        /** 修改RememberMe的有效时间 */
        CookieRememberMeManager rememberMeManager = (CookieRememberMeManager) securityManager.getRememberMeManager();
        rememberMeManager.getCookie().setMaxAge(600);
        //配置缓存管理方式
        //securityManager.setCacheManager();
        return securityManager;
    }

    /**
     * 自动调用配置在Spring IOC容器中 shiro bean生命周期方法
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 加入注解的使用，不加入这个注解不生效
     * 必须在配置了LifecycleBeanPostProcessor 才可用
     *
     * @param securityManager
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(WebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * Filter工厂，设置对应的过滤条件和跳转条件
     *
     * @param securityManager
     * @return
     * @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(WebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //登录页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        //登录成功跳转到首页
        shiroFilterFactoryBean.setSuccessUrl("/list");

        //没有权限默认跳转的页面，登录的用户访问了没有被授权的资源自动跳转到的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");


        /**
         * URL权限采取第一次匹配优先的方式
         *
         * 模式使用 Ant 风格模式
         * • Ant 路径通配符支持 ?、*、**，注意通配符匹配不包括目录分隔符“/”:
         * – ?:匹配一个字符，如 /admin? 将匹配 /admin1，但不 匹配 /admin 或 /admin/;
         * – *:匹配零个或多个字符串，如 /admin 将匹配 /admin、 /admin123，但不匹配 /admin/1;
         * – **:匹配路径中的零个或多个路径，如 /admin/** 将匹 配 /admin/a 或 /admin/a/b
         *
         * @see org.apache.shiro.web.filter.mgt.DefaultFilter
         * Filter	        解释
         * anon	            无参，开放权限，可以理解为匿名用户或游客
         * authc	        无参，需要认证
         * logout	        无参，注销，执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
         * authcBasic	    无参，表示 httpBasic 认证
         * user	            无参，表示必须存在用户，当登入操作时不做检查
         * ssl	            无参，表示安全的URL请求，协议为 https
         * perms[user]	    参数可写多个，表示需要某个或某些权限才能通过，多个参数时写 perms["user, admin"]，当有多个参数时必须每个参数都通过才算通过
         * roles[admin]	    参数可写多个，表示是某个或某些角色才能通过，多个参数时写 roles["admin，user"]，当有多个参数时必须每个参数都通过才算通过
         * rest[user]	    根据请求的方法，相当于 perms[user:method]，其中 method 为 post，get，delete 等
         * port[8081]	    当请求的URL端口不是8081时，跳转到schemal://serverName:8081?queryString 其中 schmal 是协议 http 或 https 等等，serverName 是你访问的 Host，8081 是 Port 端口，queryString 是你访问的 URL 里的 ? 后面的参数

         */
        Map<String, String> map = new LinkedHashMap<>();
        //匿名访问登录页面
        map.put("/loginIndex*", "anon");
        //匿名访问登录接口
        map.put("/userLogin*", "anon");

        //设置用户角色
        map.put("/adminPage", "roles[admin]");
        map.put("/userPage", "roles[user]");

        //登出
        map.put("/logout", "logout");
        //对所有用户认证
        map.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}